﻿namespace System.Dynamic
{
	using System.Collections.Generic;
	using System.Linq;
	using System.Reflection;

	/// <summary>
	/// Represents a proxy that distributes operations across multiple objects of the same kind.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	internal class LoadBalancerObject<T> : DynamicObject
	{
		public LoadBalancerObject(params T[] objects)
		{
			if (objects == null)
				throw new ArgumentNullException("objects");

			if (!objects.Any())
				throw new ArgumentException("The provided collection of objects is empty.");

			this.objects = objects;
			nextInstanceIndex = 0;
		}

		T[] objects;
		int nextInstanceIndex;

		/// <summary>
		/// Advances instance pointer to the next one.
		/// </summary>
		protected virtual void AdvanceInstanceIndex()
		{
			nextInstanceIndex = (nextInstanceIndex + 1) % objects.Length;
		}

		/// <summary>
		/// Gets the current instance.
		/// </summary>
		protected sealed object GetNextInstance()
		{
			return objects[nextInstanceIndex];
		}

		public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
		{
			// get next instance as a delegate
			object o = this.GetNextInstance();
			Delegate @delegate = o as Delegate;

			if (@delegate == null)
			{
				result = null;
				return false;
			}

			// invoke
			result = @delegate.DynamicInvoke(args);

			// advance index
			this.AdvanceInstanceIndex();

			return true;
		}

		public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
		{
			// resolve method
			MethodInfo mi = typeof(T).GetMethod(binder.Name, args.Select(a => a.GetType()).ToArray());

			if (mi == null)
			{
				result = null;
				return false;
			}

			// get value
			object o = this.GetNextInstance();
			result = mi.Invoke(o, args);

			// advance index
			this.AdvanceInstanceIndex();

			return true;
		}

		public override IEnumerable<string> GetDynamicMemberNames()
		{
			return typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance).Select(m => m.Name);
		}
	}
}
